1.2-变量与类型
Create by fall on 18 Nov 2020
Recently revised in 28 Aug 2025
数据类型
- Data Types(六个数据类型):undefined、Boolean、Number、String、BigInt、Symbol
- Structural Types (两个构造类型):Object、Function
- Structural Root (一个原始数据类型):null
null & undefined
null
表示一个空指针,js 中的数据在底层是以二进制存储,如果前三位为 0,那么就会判定为 object,而 null 的所有都为 0。 这也是使用 typeof 操作符检测 null 会返回 object 的原因
定义一个变量在将来用于保存对象,那么最好将变量初始化为 null
undefined
在任意方法中,如果没有声明返回值,那么就会默认返回 undefined 表示什么都没有返回。
在任意对象中,如果想要获取一个属性,但是这个属性并没有声明时,也会返回 undefined。
undefined表示对这个要取得的值,什么都没做,获取到是因为 js 的默认行为。null证明你把它进行了初始化,但没有对数据进行初始化以外的任何操作。
我们一般不会把变量赋值为 undefined,这样可以保证所有值为 undefined 的变量,都是从未赋值的自然状态。所以我们声明对象上的属性时,如果暂时没有值,会声明为 null
Boolean
由布尔对象存放的值或者要转换成布尔值的值。
返回值 当作为一个构造函数(带有运算符 new)调用时,Boolean() 将把它的参数转换成一个布尔值,并且返回一个包含该值的 Boolean 对象。如果作为一个函数(不带有运算符 new)调用的,Boolean()只将它的参数转换成一个原始的布尔值,并且返回这个值。
false 以及 0、NaN、null、空字符串 ''(注意,不是空格,而是空的字符串)、[](空数组)和 undefined 都将转换成 false。
字符串 "false" 以及其他的对象和数组都会被转换成 true。
Number
var num = 233
console.log(typeof num) // 'number'
声明不同进制数据
二进制(binary):前缀为 0b。
八进制(octonary):前面添加 0 或者是 0o (尽量不单独使用 0 ,会在 strict 模式报错)
十六进制(hexadecimal,简写 HEX):前面添加 0x 即可
声明 Number 类型的数据时,可以通过前缀生明,二进制(0b)和八进制(0o)的数据
var a = 1000 // 声明十进制
var b = 0b1011010 // 声明二进制的数据
var c = 0o152437 // 声明八进制的数据
var d = 0x9BC // 声明十六进制的数据
0b1011010 === 90 // true
如果计算的数值超过 JavaScript 计算的数值范围,会被自动转换为 Infinity 值(有正负之分),比如说 100/0 就是 Infinity,并且不能参与下一次计算。而区分 +0 和 -0 的方式,正是检测 1/x 是 Infinity 还是 -Infinity。
特殊符号
NaN
Not a Number 用来表示一个本来要返回数值的操作数未返回数值的情况避免抛出错误。
比如说,用 10-"a" 就是 NaN
- 任何涉及 NaN 操作,比如
NaN/10都会返回NaN - NaN 和任何值都不相等,包括本身(NaN == NaN 为false)
Infinity
1/0 是 Infinity,表示无限大的数字
静态方法
判断数值是否有限
确定一个数是否是有限的,可以使用 isFinite() 函数。
Number.isFinite(-2.9) // true
Number.isFinite(NaN) // false
Number.isFinite('') // false
Number.isFinite(false) // false
Number.isFinite(Infinity) // false
Number.isFinite('a'/0) // false
检测是否为NaN
Number.isNaN() 接受一个参数,检测是否为NaN。
Number.isNaN(NaN) // true
Number.isNaN('a'/0) // true
Number.isNaN('NaN') // false
保留指定位数的小数
parseInt('12a') // 12
parseInt('a12') // NaN
parseInt('') // NaN
parseInt('0xA') // 10,0x开头的将会被当成十六进制数
将字符串转化为整数 || 对数据取整
Number.parseInt('134zc.d') // parseInt 默认使用十进制进行解析字符串 134
Number.parseInt('134zc.d',8) // 以 8 进制的格式解析字符串。输出 10 进制的数
把字符串转换为浮点数
Number.parseFloat("63.25ccz2xz") 转换之后,是63.25
判断一个数是否是整数
Number.isInteger(25.0) // true
Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false
Number.isInteger(3.0000000000000002) // true
// 如果对数据的精度要求非常高,不建议使用
可接受的误差范围
Number.EPSILON 表示数据的可接受误差范围
0.1+0.7 == 0.8 // false 0.1+0.7 在JS中计算结果为0.7999999999999999,两者不相等,所以需要Number.EPSILON
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // true
安全数值
判断一个数值是否在最大安全数值,或者最小安全数值内
Number.MAX_SAFE_INTEGER === 2 ** 53 -1 // true
Number.MAX_SAFE_INTEGER === 9007199254740991 // true
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true
Number.isSafeInteger(2) // true
Number.isSafeInteger('2') // false
Number.isSafeInteger(Infinity) // false
实例方法
Number.prototype.tofixed()
保留有效数字,返回值的类型为 string
Number.prototype.toString()
将数字转换为字符串,返回值的类型为 string,可以通过输入数字表示采用什么进制转换
Number.prototype.toExponential()
科学记数法进行技术,保留两位有效数字,输出字符串,最多保留 14 位有效数字
22.14876.toFixed(2) // 保留小数点后两位数字
// "22.15"
22.14876.toString(16) // 将该数字转换为 16 进制
// "16.261522a6f3f5"
2243.14876.toExponential() // "2.24314876e+3"
2243.14876.toExponential(4) // "2.2431e+3"
String
String 有最大长度是 2^53 - 1,这在一般开发中都是够用的,但是有趣的是,这个所谓最大长度,并不完全是你理解中的字符数。一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。
String 上的所有方法都只能返回新的字符串,不修改原字符串。
ES6 之后,String 类型的数据可以迭代,迭代时,可以识别大于 \uffff 的码点(for 循环时,无法识别)。
- 迭代:对象上有迭代器
Symbol.iterator属性,使用for...of用于迭代 - 循环:三步循环
for
字符串拼接
特性
// 跨行声明字符串,需添加'\'进行转译
var str1 = 'hellow \
mybro';
// ES6 新的字符串声明符号,无需添加转译就可直接换行
var str2 = `hellow
mybro`;
// 新的的字符串拼接方法,以及函数中传入对象
function file({name,age,gender}){
console.log(`我叫${name},今年${age}岁,性别${gender}`);
};
file({name:'小明', gender:'男', age:22});
// 循环 emoji
let str = '😀🤣😜😍🤗🤔'
for (const emoji of str) {
console.log(emoji) // 依次输出😀 🤣 😜 😍 🤗 🤔
}
for (let i = 0, l = str.length; i < l; i++) {
console.log(str[i]) // 不能正确输出表情
}
Unicode
ES6 加强了对 Unicode 的支持,可以使用 \uxxxx 的形式表示一个字符
const \u0061 = '漂亮'
// 等价于 a = 漂亮
这种写法只支持从 \u0000 到 \uffff 的字符,超出这个范围必须使用双字节的形式表示,也可以可以使用大括号表示。
"\uD842\uDFB7" //
'\u{1F680}' === '\uD83D\uDE80' // true
'\u{1F680}' === '🚀'
UTF-8 标准规定,0xD800 到 0xDFFF 之间的码点,不能单独使用,必须配对使用。比如:\uD834\uDF06 是两个码点,配对使用,表示 𝌆。并且在 JSON.stringify 时,会返回转义字符串,以便应用自己决定之后的操作。
// 为了使 JSON.stringify()
JSON.stringify('\u{D834}') // '"\\uD834"'
JSON.stringify('\uDF06\uD834') // '"\\udf06\\ud834"'
实例方法
因为实例方法有点多,我个人分为下面四个类进行整理
- 查找判断类,用来查找索引,或者是判断是否出现某些关键字
- 获取内容类,用来截取一部分的文本内容
- 转换操作类,对数据进行处理转换,比如说大小写转换,除去字符串中的空格
- 转码编码类,对编码进行操作
查找判断类
查找关键词出现位置
str.indexOf('key',start)
str.lastIndexOf('keyword')
从 start 开始查找关键词 key ,查找失败返回 -1 ;start 可以省略
var str = '从前有一座山,山上有座庙'
console.log(str.indexOf('有',4)) // 基本用法
判断关键字是否符合
str.search(/正则/)
返回关键词位置,找不到返回 -1 (此方法不支持正则关键字g,毕竟只查找一个)
特定位置包含特定字符
let s = 'Hello world!'
s.includes('o') // true 是否包含该字符
s.startsWith('Hello') // true 是否以该字符开始
s.endsWith('!') // true 是否以该字符结束
获取内容类
获取单个字符
str.charAt(i)获取第i个字符str.at(i)获取第 i 个字符
'I am a fox'.charAt(-1) // ""
'I am a fox'.charAt(0) // I
'I am a fox'.at(-1) // "x"
'I am a fox'.at(-1) // "I"
截取多个字符
str.slice(start,end)截取[start,end)间的数据,支持负数,此时为从后向前截取,不传end则向后截取所有字符。str.substring(start,end)截取[start,end)间的数据,不支持负数str.substr(start,i)从 start 开始截取i个数据
获取所有符合内容
str.match(/正则/g)
找不到则返回 null ,找到返回包含所有关键词的数组
var str = '从前有一座山,山上有座庙'
str.match(/有/g) // ['有', '有']
str.match('有') // 有 index input groups 属性的数组对象 没找到就返回 null
通过迭代器取出所有匹配
str.matchAll()
let str = 'what is your name'
let reg = /a/g
let iterable = str.matchAll(reg)
for(let i of iterable){
console.log(i)
}
// ["a"]
// ["a"]
获取重复 n 次后的字符串
// repeat(n) 将当前字符串重复 n 次后,返回一个新字符串:
'x'.repeat(2) // 'xx'
'x'.repeat(1.9) // 'x'
'x'.repeat(NaN) // ''
'x'.repeat(undefined) // ''
'x'.repeat('2a') // ''
'x'.repeat(-0.6) // '',解释:0 ~ 1 之间的小数相当于 0
'x'.repeat(-2) // RangeError
'x'.repeat(Infinity) // RangeError
转换操作类
拼接字符串
str.concat()
let cc = 'loofd'.concat('fff;')
大小写转换
str.toUpperCase()将字符串转换为大写str.toLowerCase()将字符串转换为小写
字符串分割
str.split('str')
以 str 为分隔条件,将字符串分割,如果内容为空,则全部分割。返回值是一个数组
str.split(/正则/)
左侧插入数值
str.padStart(length[,padStr]) 返回字符串长度填充到 length 的数组,如果超过,不会进行填充。
- 右侧插入值类似于左侧插入
str.padEnd()
'abc'.padStart(5,2) // '22abc'
'2a'.padStart(8,{}) // "{objec2a" 是的,执行后的结果就是这个
'2a'.padStart(8,undefined) // "2a"
'abcdef'.padStart(4,undefined) // "abcdef"
str.replace('oldStr','newStr') 将旧字符串替换为新的字符串
const text = 'i have a dog,the dog really adorable'
text.replace('dog','cat') // "i have a cat,the dog really adorable" 只会匹配一次
text.replace(/Dog/.ig,'cat') // "i have a dog,the dog really adorable" 正则匹配,配合关键字可以全局匹配
去除字符串空格
str.trimStart()、str.trimLeft()消除字符串头部空格str.trimEnd()、str.trimRight()消除字符串尾部空格str.trim()消除开始和结尾的空格
let str = ' 31234 hello world'
str.trimStart()
' fall '.trimStart().trimEnd() // 'fall'
' fall '.trim() // 'fall'
转码编码类
str.charCodeAt(i)第i个字符的编码
charCodeAt 和 formCharCode 互相为反向操作
// String.__proto__.formCharCode() 返回指定字符组成的新字符串
'帅'.charCodeAt()
// 24069
String.fromCharCode(20426)
// 俊
静态方法
// String.fromCodePoint() 用于从 Unicode 码点返回对应字符
String.fromCharCode(0x1f600) // ""
String.fromCodePoint(0x1f600) // "😀"
// String.raw() 返回将字符串所有变量替换且对斜杠进行转义的结果
console.log(String.raw`Hi\t\n${2+3}!`); // Hi\t\n5!
// 返回字符的十进制码点,对于 Unicode 大于 0xFFFF 的字符,会被认为是2个字符,十进制码点转成十六进制可以使用 toString(16)
let emoji = '🤣'
emoji.length // 2
emoji.charCodeAt(0).toString(16) // 'd83d'
emoji.charCodeAt(1).toString(16) // 'de00'
String.fromCodePoint(0xd83d, 0xde00) === '🤣' // true
BigInt
可以表示大于 2^53-1 以上的整数。BigInt 可以表示任意大的数字,并且进行四则运算
// 以字母 n 结尾可以定义 BigInt
const num1 = 23n
const num2 = BigInt(66)
其他特性
10n == 10 // true
10n === 10 //false
10n > 8 // true
10 + Number(25n) // 35
10 + 20n // TypeError
100n + BigInt(55) // 155n
Symbol
symbol 作为一个原始数据类型,用来表示独一无二的值
let s1 = Symbol('fall')
let s2 = Symbol('fall')
s1 == s2 // false
s1 === s2 // false
typeof s1 // 'symbol'
引入 Symbol 的初衷是为了让它作为对象的属性名,避免属性名的冲突
let foo = Symbol('foo')
let obj = {
[foo]: 'foo1',
foo: 'foo2'
}
obj[foo] // 'foo1'
obj.foo // 'foo2'
Symbol 书写的数据不会被各种循环所枚举
let person ={
name:'fall',
[Symbol('age')]:'23',
}
for (item in person){
console.log(item)
}
Object.keys(person) // ['name']
Object.getOwnPropertyNames(person) // ['name']
JSON.stringify(person) // '{"name":"布兰"}'
// 可以通过专有的获取Symbol进行获取
Object.getOwnPropertSymbol(person) // [Symbol('person')]
属性
Symbol.length
属性值为 0。
除了 length 属性之外,其他的属性,都要作为 key 绑定到对象上,以此对相应的方法进行控制
Symbol.hasInstance 用于定义对象的 instanceof 操作符行为。当一个对象的原型链中存在 Symbol.hasInstance 方法时,该对象可以被 instanceof 运算符使用。
可以通过将该属性放置到对象内,用来检测
class Foo {
static [Symbol.hasInstance](receive){
return receive instanceof Array
}
}
// 调用 instanceof 的时候,会执行上面的静态方法
console.log([] instanceof Foo); // true
console.log({} instanceof Foo); // false
Symbol.iterator
Symbol.iterator 表示对象的默认迭代器方法。该方法返回一个迭代器对象,可以用于遍历该对象的所有可遍历属性。
可以通过定义一个对象上面的 Symbol.iterator 属性,以便能够使用 for...of 语句进行遍历
const obj = { a: 1, b: 2 };
// 返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。
for (const key of Object.getOwnPropertyNames(obj)) {
console.log(key);
}
// Output:
// 'a'
// 'b'
obj[Symbol.iterator] = function* () {
for (const key of Object.keys(this)) {
yield key;
}
}
for (const key of obj) {
console.log(key);
}
// Output:
// 'a'
// 'b'
Symbol.isConcatSpreadable
可以通过定义一个数组上面的 Symbol.iterator 属性,用来定义 concat 时,是否会被展开
用于定义数组
const arr1 = [1, 2];
const arr2 = [3, 4];
arr2[Symbol.isConcatSpreadable] = false
arr1.concat(arr2)
// Array(3) [ 1, 2, [ 3, 4 ] ]
Symbol.toPrimitive
定义被强制类型转换时的行为
const obj = {
valueOf() {
return 1;
},
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 2;
} else if (hint === 'string') {
return 'foo';
} else {
return 'default';
}
}
}
console.log(0+obj); // 2
console.log(`${obj}`); // 'foo'
console.log(obj + ''); // 'default'
Symbol.toStringTag
定义调用该对象的 toString() 方法时,会返回该属性对应的字符串。
class Foo {
get [Symbol.toStringTag]() {
return 'Bar';
}
}
console.log(Object.prototype.toString.call(new Foo())); // '[object Bar]'
Symbol.species 用于定义派生对象的构造函数。
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
const myArr = new MyArray(1, 2, 3);
const arr = myArr.map(x => x * 2);
console.log(arr instanceof MyArray); // false
// 派生对象的构造函数还是为 Array
console.log(arr instanceof Array); // true
Symbol.match
Symbol.replace
Symbol.search
用于调用 String.prototype.match()、String.prototype.replace()、String.prototype.search()、String.prototype.split()方法时,会调用该方法进行匹配。
自定义一个对象在调用以上方法时的行为时,可以通过定义 Symbol.replace 和 Symbol.match 方法来实现。
class Foo {
[Symbol.match](str) {
return str.indexOf('foo') !== -1;
}
[Symbol.replace](str, replacement) {
return str.replace('foo', replacement);
}
}
console.log('foobar'.match(new Foo())); // true
console.log('barbaz'.match(new Foo())); // false
console.log('foobar'.replace(new Foo(), 'baz')); // 'bazbar'
console.log('barbaz'.replace(new Foo(), 'baz')); // 'barbaz'
Symbol.unscopables
用于定义对象在使用 with 语句时的行为。如果一个对象定义了 Symbol.unscopables 属性,则在使用 with 语句时,该对象的指定属性将不会被绑定到 with 语句的环境中。
const obj = {
a: 1,
b: 2,
c: 3,
[Symbol.unscopables]: {
c: true
}
};
with (obj) {
console.log(a); // 1
console.log(b); // 2
console.log(c); // ReferenceError: c is not defined
}
静态方法
Symbol.for()
Symbol.for(key) 会维护一个全局注册表, 根据输入的内容 key 确定唯一性,如果存在,则返回已存在的 Symbol 对象,如果不存在,创建新的 Symbol,并通过给定的 key 将其存储在注册表中。
Symbol.for() 的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同一个值。
let sym1 = Symbol('fall')
let sym2 = Symbol.for('fall')
let sym3 = Symbol.for('fall')
sym1 === sym2 // false
sym2 === sym3 // true
sym1 === sym3 // false
Symbol.keyFor()
使用 Symbol.for() 存入全局注册表的内容,可以通过该方法获取到对应的 key,如果全局注册表为空
实例方法
Symbol.prototype.description
// 描述属性
let cc = Symbol('ssr')
cc.description // "ssr"
Symbol.prototype.toString()
const symbol = Symbol('foo');
console.log(symbol.toString()); // 'Symbol(foo)'
Symbol.prototype.valueOf()
方法会返回 Symbol 值本身。
示例代码:
const symbol = Symbol('foo');
console.log(symbol.valueOf()); // Symbol(foo)
参考文章
| 文章名称 | 文章地址 |
|---|---|
| 大海我来了 | 「建议收藏」送你一份精心总结的3万字ES6实用指南(上) |
| JavaScript 数据类型和数据结构 | https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures |
| 布衣1983 | 都这么多年了,作为一个前端的你是不是连Symbol都不会用 |